인터럽트, 트랩, 예외.. 결론은 이벤트(1) - 운영체제 이벤트
이벤트
이번 장에서는 운영체제에서 정말 중요한 개념인 이벤트에 대해서 배워보도록 하자.
또한 운영체제를 공부해본 사람들에게도 다시 한번 헷갈리는 개념을 되짚을 수 있는 글이 될 것이다.
또한 이 글을 읽고 나면 운영체제에 대한 아래의 지식을 얻을 수 있을 것이다
- 프로세스의 정체를 알기 위한 기본 지식을 얻을 수 있다
- 컴퓨터에서 키보드 마우스가 어떻게 작동하는지 알 수 있다
- 운영체제를 이해하는 데 중요한 인터럽트, 트랩, 예외가 무엇인지 알 수 있다
인터럽트? 트랩? 예외?
제목에서도 알 수 있듯이 인터럽트, 트랩, 예외 등의 정체는 이벤트라는 단어로써 정리될 수 있다.
그럼 이벤트라는 것은 어디에 쓰이고 왜 필요한 것일까?
그것을 이해하기 위해서 다시 한번 튜링 머신으로 돌아가 보자.
위 사진은 초창기 컴퓨터인 에니악의 모습이다.
과거 컴퓨터와 현재의 컴퓨터는 많은 점에서 차이점이 존재하겠지만, 우리 눈에 보이는 가장 큰 차이점은 바로 키보드, 마우스가 보이지 않는 것이다.
그렇다면 키보드, 마우스 없이 옛날 사람들은 컴퓨터를 어떻게 사용한 것일까?
과거의 컴퓨터는 하나의 일
사실 과거 컴퓨터는 하나의 일 만을 담당하여 계산을 진행하였다.
사람들이 손으로 한 땀 한 땀 코드를 작성하고, 그것을 입력으로 넣으면 기대한 출력값이 출력해주는 기계였다.
[한 땀 한 땀 손으로 짜신 코드를 내밀고 계시는 마가렛 헤밀튼]
만약 로켓의 궤도를 계산하거나, 로켓을 달에 착륙시키기 위한 코드를 넣으면 어떻게 자세를 조정하고, 궤도를 수정해야 하는지에 대한 값이 출력으로 나오는 것이다.
즉 코드와 입력값만 있으면 원하는 답을 출력으로 얻을 수 있었으므로 키보드와 마우스가 필요하지 않았던 것이다.
컴퓨터가 궤도 계산만을 위한 기계였다면 아무런 문제가 없었겠지만, 사람들이 이 대단한 물건을 하나의 계산을 위한 도구가 아니라 조금 더 다양한 일을 할 수 있기를 원했다.
현재의 컴퓨터
여기서 다양한 일이란 무엇일까?
중요한 것은 컴퓨터가 계산을 한다는 본질은 변하지 않지만, 현대의 우리는 그 계산을 통하여 엑셀(통계)를 계산한다든가, 또 다른 계산의 결과물인 게임을 하고 있다.
현재와 과거의 차이는 바로, 우리는 게임을 하면서 웹서핑을 하고, 엑셀을 하면서 파워포인트를 만들면서 디스코드로 통화를 하고 있다는 것이다.
과거의 엔지니어들은 다양한 일, 즉 게임과 통화와 학교 숙제를 동시에 하는 것을 꿈꿨고 그 결과물이 현재의 컴퓨터인 것이다.
어떻게?
그렇다면 이 컴퓨터는 어떻게 여러 일을 할 수 있는 것일까?
도대체 우리는 어떻게 게임과 강의를 동시에 들으면서 친구와 통화를 할 수 있는 것일까?
아래로 스크롤을 내리기 전에 다음과 같은 힌트를 기반으로 생각해보자. 도대체 어떻게 엔지니어들은 이 문제를 해결했을까?
- 여러 일을 하고 있는 현대의 컴퓨터들도 사실은 하나의 계산을 하고 있다(1개의 코어를 사용하는 경우)
- 컴퓨터가 계산을 한다는 본질은 바뀌지 않았다
- CPU는 사람이 체감할 수 있는 것보다 훨~~씬 빠르다
정답은
컴퓨터가 여러 일을 잠깐씩 하는 것이다.
즉 위와 같은 흐름으로 진행하고 있는 것이다.
대충 예상할 수 있는 내용이고, 컴퓨터는 여러 일을 하는 것처럼 보여준다 라는 말을 들은 적이 있는 사람도 있을 수 있다.
이제 조금 더 깊은 내용으로 들어가 보자.
지금까지의 내용을 정리하면 프로그램이 번갈아 가면서 잠깐씩 실행된다고 하였다.
그렇다면
- 이러한 프로그램(파워포인트, 디스코드, 엑셀, 게임)의 전환이 어떻게 이루어지는 것일까?
- 무엇을 기준으로 전환이 이루어지는 것인가?
여기서 잠깐 용어정리를 하면서 OS 이론에 대해서 정리하자면 실제로 동작중인 프로그램을 프로세스라고 하고 CPU에서 동작하는 프로그램의 전환 과정을 컨텍스트 스위칭 이라고 정의한다.
그렇다면 다시 내용으로 돌아와서 움직이는 프로그램(프로세스)의 전환(컨텍스트 스위칭)은 어떻게 그리고 무엇을 기준으로 이루어 지는 것일까?
컨텍스트 스위칭의 본질은 이후에 프로세스의 본질에서 다루기로 하고, 우리는 전환(컨텍스트 스위칭)이 이루어지는 방식에 집중하자.
정답은 의외로 간단하다.
바로 바꾸어 줘 라는 요청을 발생시키는 것이다.
??? 라는 생각이 들 수 있지만, 결론은 다음과 같다.
- 무엇을 기준으로 전환이 이루어지는 것인가? --> 바꾸어 줘 라는 요청을 기준으로
- 프로세스의 전환이 어떻게 이루어지는 것일까? --> 바꾸어 줘 라는 요청에 대해 행동(핸들링)함으로써
이러한 요청을 이벤트 라고 하고 요청을 해결하기 위한 행동을 이벤트에 대한 핸들링이라고 한다.
이벤트와 이벤트 핸들러
지금까지 이벤트와 이벤트 핸들러에 대해서 알아보았다.
지금까지의 내용을 잘 따라왔으면 이제 키보드와 마우스를 누르는 것 또한 이벤트에 해당한다는 것을 예상할 수 있다.
또한, 발생한 이벤트를 컴퓨터가 그 이벤트를 처리하는 루틴(이벤트 핸들러)을 부르는 것을 예상할 수 있다.
그리고 핸들러의 결과로써 컴퓨터는 우리의 입력을 받고있음을 예상할 수 있다.
이렇듯, 프로세스의 전환(컨텍스트 스위칭) 또한 어떠한 이벤트가 발생하고 그 이벤트를 처리하는 핸들러를 호출함으로써 이루어진다는 것을 알 수 있다.
이제 그 자세한 내용을 16bit CPU를 예로 알아보자.
[출처 - https://en.wikipedia.org/wiki/Intel_8086 ]
16bit 컴퓨터의 대표격인 8086의 경우 INTR(Interrupt request) 와 NMI(Non-maskable Interrupt) 라는 핀이 존재하고 이곳에 신호에 따라서 이벤트의 발생을 확인한다.
또한 이벤트가 발생하면 이벤트의 종류에 따라서 이벤트를 처리하는 이벤트 핸들러를 호출하게 되는데 이 이벤트 핸들러의 정체는 사실 함수이다.
[거짓말이 조금 들어있는 그림이다-8086에는 IDTR이 존재하지 않는 것에 주의]
위 그림이 방금 설명에 전체적인 흐름이다.
IRQ 1이 키보드에 대한 이벤트라고 가정해보자.
이벤트 핸들러는 OS(커널)의 초반 단계에서 다음과 같은 흐름을 거친다.
- CPU는 IDTR 레지스터에 이벤트에 대한 핸들러들의 주소를 보관하고 있는 테이블의 주소를 가지는데 OS(커널) 시작 단계에서 IDTR의 값을 IDT Table의 주소로 설정한다
- OS(커널)의 코드에 키보드에 대한 이벤트를 처리하는(e.g. 키보드에 대한 입력이 들어오면 키보드와 통신을 진행하는) 함수를 작성한다.
- 마지막으로 이 함수를 IRQ 1에 대한 핸들러로 등록해두면 된다
이때 핸들러는 함수에 대한 주소를 가짐으로 IDT는 C 언어에서 함수의 포인터들에 대한 리스트를 가지는 함수 포인터 리스트와 동일한 것으로 볼 수 있다.
이제 키보드를 누름으로써 이벤트가 발생하면 CPU는 하는 일을 멈추고, 생성된 이벤트를 해결하기위해 키보드 입력에 해당하는 이벤트 핸들러를 호출한다.
이벤트 핸들러를 호출하는 것은 함수를 호출하는 것이고, 함수의 진행을 통해 키보드와 잠깐 통신을 진행한 뒤 원래 하던 일로 복귀한다.
부록
참조 - 프로세스의 전환
여기서 앞으로 설명할 프로세스의 스케쥴링을 다루기 위해 위에서 언급한 프로세스의 전한에 대해서 약간만 언급하고 가도록 하자.
프로세스의 전환은 보통 타이머를 기준으로 실행된다.
최신 CPU는 내부적으로 알람을 가지고 주기적으로 울도록 설정하는 것이 가능하다.
예를 들어 알람을 1초마다 울리도록 설정해두었다고 하자, 그렇다면 만약 우리가 어떠한 프로세스가 실행된 시간을 기록해둘 수 있다면 다음과 같은 로직을 생각해볼 수 있다.
1초 마다 알람이 울릴 때 이벤트 핸들러로서 프로세스들의 실행 시간을 확인하는 함수를 등록해두자.
알람이 울렸을 때 프로세서의 실행 시간이 너무 길면 다른 프로세스를 실행하도록 할 수 있다!
굉장히 추상적인 설명이긴 하지만 스케쥴러는 보통 이러한 방식으로 동작한다.
인터럽트, 트랩, 예외 - 이벤트의 종류
지금까지 이벤트가 무엇인지 그리고 그에 대한 자세한 예시를 살펴보았다.
그렇다면 그냥 이벤트라는 단어를 쓸 것이지 왜 인터럽트, 트랩, 예외라는 헷갈리게 다양한 용어를 쓰는지를 잠깐 살펴보고 가자.
위에서는 키보드를 예시로 들었지만, 그 외에 다양한 이벤트들이 존재할 수 있다.
계속 언급했듯이 OS는 리소스의 관리가 중요한 키워드이고, 프로세스의 관리도 그 중 하나이다.
만약 프로세스에서 에러가 발생하거나, 프로세스 자신이 종료되었을 때를 가정해보자.
프로세스는 자신의 에러 여부 또는 종료여부를 알리기 위해서 인위적으로 이벤트를 발생시킬 수 있다.
또 프로세스가 OS에게 어떠한 일을 부탁하고 싶은 경우도 존재할 수 있다.(대표적인 예시로써 앞으로 배울 시스템 콜이 있을 수 있다)
이러한 목표를 달성하기 위해서 CPU에는 INT n
라는 Interrupt를 인위적으로 발생시키는 명령어가 존재한다.
여기서 키보드와 프로세스가 발생시키는 이벤트를 각각 하드웨어에 의한 이벤트 그리고 소프트웨어에 의한 이벤트로써 hardware interrupt, software interrupt를 구분하여 부른다.
그리고 그 외에 트랩과 예외(Exception)와 같은 용어는, 사실 정확한 구분 기준이 존재하지 않는다.(사실 여러 CPU회사(Intel, ARM, AMD) 의 개발자 매뉴얼에서 부르는 용어를 가져다 쓴다에 가깝다고 보아야한다)
하지만 보통 트랩을 software interrupt 라고 하고. 수를 0 으로 나누거나, 존재하지 않는 명령어를 입력 받는 등의 예외적인 상황에 대한 이벤트를 예외(Excpetion)이라고 한다.
용어 정리
위에서 설명한 이벤트에 대한 용어 이외에도 이 장에서 다룬 여러 같은 개념을 다른 용어를 사용하여 불린다.
앞으로 들을 일이 있을 수 있기 때문에 정리를 하고 넘어가자.
IVT(Interrupt Vector Table)
보통 Interrupt에 대한 핸들러를 등록하는 테이블을 IVT(Interrupt Vector Table)이라고 부른다.
위에서 우리는 이 테이블들의 정체가 사실은 함수 포인터의 리스트인 것을 보았다.
이 함수 포인터를 보통 운영체제 이론에서는 Interrupt Vector 라고 정의한다.
그리고 그 테이블을 Interrupt Vector Table이라고 부른다.
ISR(Interrupt Service Routine)
또 다른 용어로는 ISR(Interrup Service Routine)이 존재한다.
ISR이 경우 위에서 설명한 키보드에 대한 이벤트를 처리하는 핸들러와 비슷한 개념이다.
이벤트 발생 시 핸들러를 호출하고 그 결과 리턴하는 과정을 Interrupt에 대한 Service를 진행한다고 말하고, 전체 흐름(루틴)을 Interrupt Service Routine이라고 한다.
IRQ(Interrupt Request)
마지막 용어는 위에서 사용한 IRQ이다.
IRQ는 단어 그대로 Interrupt의 요청을 의미한다.
위에서 보았듯이 IRQ에는 번호가 존재하고 각 번호에 등록된 핸들러를 호출하는데 이때 IRQ를 발생시키는 것을 Interrupt에 대한 Request를 요청했다 또는 n번째 IRQ를 생성했다 라고 말한다.
8086 조금 더 자세히 살펴보기
8086을 살펴볼 때 인터럽트 발생을 위해 INTR(Interrupt request) 와 NMI(Non-maskable Interrupt)라는 2개의 핀이 존재한다고 했는데 우리는 INTR만을 살펴보았다.
물론 INTR 뿐 아니라 NMI또한 같은 인터럽트와 관련되어있다.
여기서는 이벤트의 종류와 관리에 대한 자세한 내용을 살펴보자.
자세한 내용은 [[iAPX_286_Programmers_Reference.pdf]]을 살펴보도록 하자.
사실 인터럽트의 종류는 조금 더 다양하게 세분화되는데, 세분화의 기준은 보통 다음과 같이 정해진다.
- 인터럽트의 주체(소프트웨어, 하드웨어)
- Mask 가능 여부(INTR, NMI)
- 우선순위(Priority)
첫 번째의 주체는 이미 설명하였기 때문에 두 번째 부터설명을 진행하자.
Mask란 바로 인터럽트의 발생 여부를 결정하는 것이다.
만약 키보드에 이벤트를 처리 중에 매번 다른 이벤트가 발생하여 키보드의 입력이 무시되는 경우를 생각해보자(생각만해도 답답하지 않은가).
이러한 경우를 대비해서 CPU에는 인터럽트의 발생을 무시하도록 설정하는 명령어(CLI(Clear Interrupt Flag)
) 또는 특정 인터럽트에 대한 핸들러를 호출을 무시하도록 하는 등의 설정이 가능하다.
이러한 인터럽트의 발생을 조절하는 것을 Masking을 한다고 하고, Masking 된 인터럽트는 무시된다.
하지만 특정 인터럽트는 Masking을 하는 것이 불가능 한데(즉 무시하는 것이 불가능한) 그러한 인터럽트를 Non-maskble Interrupt라고 한다.
예를 들어서 자동차에 들어가는 운영체제를 생각해보자, 자동차의 핸들을 Input이라고 가정하면, 핸들링에 대한 입력을 무시하는 것이 단순히 키보드에 대한 입력을 무시하는 것보다 위험한 것임을 알 수 있을 것이다.
마지막으로 인터럽트는 우선순위에 따라서 나눌 수 있다.
만약 동시에 다수의 이벤트가 발생하면 우선순위가 높은 인터럽트가 동작하도록 하는 등의 기능을 위한 우선순위가 존재한다.
자동차의 예시와 같이 핸들링에 대한 입력을 우선순위가 높은 이벤트로써 생각해볼 수 있다.
만약 인터럽트가 동시에 발생하면?
만약 다수의 인터럽트가 동시에 발생하면 CPU는 그것을 어떻게 처리할까?
이 문제에 대답은 사실 구현에 따라 다르다이다.
예를 들어서 동시에 발생하는 이벤트를 저장해서 순차적으로 처리하거나, 우선순위가 높은 것을 먼저 처리하거나, 나머지 이벤트를 무시하거나 하는 등의 구현이 있을 수 있다.(즉, 운영체제를 이해하기 위해서는 결국 하드웨어와 친해져야한다...)
우리는 위에서 살펴본 8086을 기준으로 살펴보도록 하자.
8086는 사실 다양한 디바이스의 인터럽트 처리를 위해서 추가적인 칩인 Intel 8259를 장착하여 사용한다.
그렇다면 Intel 8259의 구현은 어떻게 되어있을까?
Intel 8259에는 두 개의 레지스터를 사용해서 서비스의 진행을 나타낸다
The IRR is used to store all the interrupt levels which are requesting service; and the ISR is used to store all the interrupt levels which are being serviced.
Intel - 8259A PROGRAMMABLE INTERRUPT CONTROLLER
즉 각 레지스터에 디바이스의 인터럽트 요청과 서비스 완료를 표시해두고, 여러 요청이 있는 경우 우선순위 등을 기준으로 먼저 인터럽트 처리를 진행한다.
더 자세한 내용은 아래를 참고하자
다양한 사용처
인터럽트는 조금 더 다양한 경우에 사용된다.
각 사용처를 조사해보면 운영체제의 여러 기능에 대해서 조금 더 자세히 알 수 있을 것이다.
- DMA: Direct Memory Access 기능
- 디버거: Single-step Execution 기능
- IPC: Inter Process Communication 프로세스간 통신
특히 디버깅을 위해 사용되는 Single-step Execution을 찾아보면 흥미로울 것이다.